#include <config.h>
#include "Analyzer.h"
#include <gdkmm/pixbuf.h>

#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <dirent.h>
#include <string.h>
#include <time.h>
#include <gdk/gdkx.h>


#include <math.h>
#include <algorithm>



#include "load-graph.h"

extern Analyzer * an;


void LoadGraph::clear_background()
{
	if (this->background) {
		g_object_unref(this->background);
		this->background = NULL;
	}
}


unsigned LoadGraph::num_bars() const
{
	unsigned n;

	// keep 100 % num_bars == 0
	switch (static_cast<int>(this->draw_height / (this->fontsize + 14)))
	{
	case 0:
	case 1:
		n = 1;
		break;
	case 2:
	case 3:
		n = 2;
		break;
	case 4:
		n = 4;
		break;
	default:
		n = 5;
	}

	return n;
}



#define FRAME_WIDTH 4
void draw_background(LoadGraph *g) {
	double dash[2] = { 1.0, 2.0 };
	cairo_t *cr;
	guint i;
	unsigned num_bars;
	char *caption;
	cairo_text_extents_t extents;

	num_bars = g->num_bars();
	g->graph_dely = (g->draw_height - 15) / num_bars; /* round to int to avoid AA blur */
	g->real_draw_height = g->graph_dely * num_bars;
	g->graph_delx = (g->draw_width - 2.0 - g->rmargin - g->indent) / (LoadGraph::NUM_POINTS - 3);
	g->graph_buffer_offset = (int) (1.5 * g->graph_delx) + FRAME_WIDTH ;

	g->background = gdk_pixmap_new (GDK_DRAWABLE (g->disp->window),
					g->disp->allocation.width,
					g->disp->allocation.height,
					-1);
	cr = gdk_cairo_create (g->background);
	
	// set the background colour


	GtkStyle *style = gtk_widget_get_style (g->main_widget);
	gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]);
	cairo_paint (cr);

	/* draw frame */
	cairo_translate (cr, FRAME_WIDTH, FRAME_WIDTH);
	
	/* Draw background rectangle */
	cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
	cairo_rectangle (cr, g->rmargin + g->indent, 0,
			 g->draw_width - g->rmargin - g->indent, g->real_draw_height);
	cairo_fill(cr);
	
	cairo_set_line_width (cr, 1.0);
	cairo_set_dash (cr, dash, 2, 0);
	cairo_set_font_size (cr, g->fontsize);

	cairo_set_source_rgb (cr, .2, .2, .2);
	for (i = 0; i <= num_bars; ++i) {
		double y;

		if (i == 0)
			y = 0.5 + g->fontsize / 2.0;
		else if (i == num_bars)
			y = i * g->graph_dely + 0.5;
		else
			y = i * g->graph_dely + g->fontsize / 2.0;

		// operation orders matters so it's 0 if i == num_bars
		switch (g->type) {
			case 0:
				caption = g_strdup_printf("%d Kb/s", (int)g->max_bitrate - i * (g->max_bitrate / num_bars));
				break;
			case 1:
				caption = g_strdup_printf("%d %%", 100 - i * (100 / num_bars));
				break;
			case 2:
				caption = g_strdup_printf("%d usec", g->max_ujitter - i * (2* g->max_ujitter / num_bars));
				break;
			default:
				caption = g_strdup_printf("%d %%", 100 - i * (100 / num_bars));
				break;
		}

		cairo_text_extents (cr, caption, &extents);
		cairo_move_to (cr, g->indent - extents.width + 20, y);
		cairo_show_text (cr, caption);
		g_free (caption);


		cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
		cairo_move_to (cr, g->rmargin + g->indent - 3, i * g->graph_dely + 0.5);
		cairo_line_to (cr, g->draw_width - 0.5, i * g->graph_dely + 0.5);
	}
	cairo_stroke (cr);

	cairo_set_dash (cr, dash, 2, 1.5);

	const unsigned total_seconds = g->speed * (LoadGraph::NUM_POINTS - 2) / 1000;

	for (unsigned int i = 0; i < 7; i++) {
		double x = (i) * (g->draw_width - g->rmargin - g->indent) / 6;
		cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
		cairo_move_to (cr, (ceil(x) + 0.5) + g->rmargin + g->indent, 0.5);
		cairo_line_to (cr, (ceil(x) + 0.5) + g->rmargin + g->indent, g->real_draw_height + 4.5);
		cairo_stroke(cr);
		unsigned seconds = total_seconds - i * total_seconds / 6;
		const char* format;

			format = "%u";
		caption = g_strdup_printf(format, seconds);
		cairo_text_extents (cr, caption, &extents);
		cairo_move_to (cr, ((ceil(x) + 0.5) + g->rmargin + g->indent) - (extents.width/2), g->draw_height);

		cairo_show_text (cr, caption);
		g_free (caption);
	}

	cairo_stroke (cr);
	cairo_destroy (cr);
}

/* Redraws the backing buffer for the load graph and updates the window */
void
load_graph_draw (LoadGraph *g)
{
	/* repaint */
	gtk_widget_queue_draw (g->disp);
}

static int load_graph_update (gpointer user_data); // predeclare load_graph_update so we can compile ;)

static gboolean
load_graph_configure (GtkWidget *widget,
		      GdkEventConfigure *event,
		      gpointer data_ptr)
{
	LoadGraph * const g = static_cast<LoadGraph*>(data_ptr);
	g->draw_width = widget->allocation.width - 2 * FRAME_WIDTH;
	g->draw_height = widget->allocation.height - 2 * FRAME_WIDTH;

	g->clear_background();

	if (g->gc == NULL) {
		g->gc = gdk_gc_new (GDK_DRAWABLE (widget->window));
	}

	load_graph_draw (g);

	return TRUE;
}

static gboolean
load_graph_expose (GtkWidget *widget,
		   GdkEventExpose *event,
		   gpointer data_ptr)
{
	LoadGraph * const g = static_cast<LoadGraph*>(data_ptr);

	guint i, j;
	gdouble sample_width, x_offset;

	if (g->background == NULL) {
		draw_background(g);
	}
	gdk_draw_drawable (g->disp->window,
			   g->gc,
			   g->background,
			   0, 0, 0, 0, 
			   g->disp->allocation.width, 
			   g->disp->allocation.height);

	/* Number of pixels wide for one graph point */
	sample_width = (float)(g->draw_width - g->rmargin - g->indent) / (float)LoadGraph::NUM_POINTS;
	/* General offset */
	x_offset = g->draw_width - g->rmargin + (sample_width*2);

	/* Subframe offset */
	x_offset += g->rmargin - ((sample_width / g->frames_per_unit) * g->render_counter);

	/* draw the graph */
	cairo_t* cr;

	cr = gdk_cairo_create (g->disp->window);

	cairo_set_line_width (cr, 1);
	cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
	cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
	cairo_rectangle (cr, g->rmargin + g->indent + FRAME_WIDTH + 1, FRAME_WIDTH - 1,
			 g->draw_width - g->rmargin - g->indent - 1, g->real_draw_height + FRAME_WIDTH - 1);
	cairo_clip(cr);

	for (j = 0; j < g->n; ++j) {
		cairo_move_to (cr, x_offset, (1.0f - g->data[0][j]) * g->real_draw_height);
		cairo_set_source_rgba (cr, 0.5, 0.5, 0.7, 1);
		//gdk_cairo_set_source_color (cr, &(g->colors [j]));

		for (i = 1; i < LoadGraph::NUM_POINTS; ++i) {
			if (g->data[i][j] == -1.0f)
				continue;
			cairo_curve_to (cr, 
				       x_offset - ((i - 0.5f) * g->graph_delx),
				       (1.0f - g->data[i-1][j]) * g->real_draw_height + 3.5f,
				       x_offset - ((i - 0.5f) * g->graph_delx),
				       (1.0f - g->data[i][j]) * g->real_draw_height + 3.5f,
				       x_offset - (i * g->graph_delx),
				       (1.0f - g->data[i][j]) * g->real_draw_height + 3.5f);
		}
		cairo_stroke (cr);

	}

	cairo_destroy (cr);

	return TRUE;
}





/* Updates the load graph when the timeout expires */
static gboolean
load_graph_update (gpointer user_data)
{

	LoadGraph * const g = static_cast<LoadGraph*>(user_data);
	int kbs,nbs;
	int ujitt;
	if (g->render_counter == g->frames_per_unit - 1) {
		std::rotate(&g->data[0], &g->data[LoadGraph::NUM_POINTS - 1], &g->data[LoadGraph::NUM_POINTS]);

	}

	switch (g->type) {
		case 0: //bitrate
			kbs = (an->received_byte * 1.0f / g->update_time);
			if (kbs > g->max_bitrate*1.2f)
			{

				nbs = (kbs + g->max_bitrate) / 2;
				const float scale = 1.0f * g->max_bitrate / nbs;
				g->max_bitrate = nbs;
					for (size_t i = 0; i < LoadGraph::NUM_POINTS; i++) {
						if (g->data[i][0] >= 0.0f) {
							g->data[i][0] *= scale;
						}
					}
					g->clear_background();
			}

			g->data[0][0] = (kbs * 1.0f / g->max_bitrate);
			an->received_byte = 0;
			break;
		case 1: //error rate
			g->data[0][0] = an->packet_loss_rate;
			break;
		case 2:
			if (abs((int)an->ujitter) < 10000 && abs((int)an->ujitter) > abs(g->max_ujitter))
			{

				const float scale = g->max_ujitter *  1.0f /  abs((int)an->ujitter);
				g->max_ujitter = abs((int)an->ujitter);
				g->clear_background();
				for (size_t i = 0; i < LoadGraph::NUM_POINTS; i++) {
										if (g->data[i][0] >= 0.0f) {
											g->data[i][0] = (g->data[i][0] - 0.5) * scale + 0.5;
										}
									}

			}
			g->data[0][0] = 0.5 + 0.5*(an->ujitter * 1.0f / g->max_ujitter);
			break;
		default:
			break;
	}



	if (g->draw)
		load_graph_draw (g);

	g->render_counter++;

	if (g->render_counter >= g->frames_per_unit)
		g->render_counter = 0;

	return TRUE;
}



LoadGraph::~LoadGraph()
{
  load_graph_stop(this);

  if (this->timer_index)
    g_source_remove(this->timer_index);

  this->clear_background();
}



static gboolean
load_graph_destroy (GtkWidget *widget, gpointer data_ptr)
{
	LoadGraph * const g = static_cast<LoadGraph*>(data_ptr);

	delete g;

	return FALSE;
}


LoadGraph::LoadGraph(guint type)
  : fontsize(0.0),
    rmargin(0.0),
    indent(0.0),
    n(0),
    type(0),
    speed(0),
    draw_width(0),
    draw_height(0),
    render_counter(0),
    frames_per_unit(0),
    graph_dely(0),
    real_draw_height(0),
    graph_delx(0.0),
    graph_buffer_offset(0),
    main_widget(NULL),
    disp(NULL),
    gc(NULL),
    background(NULL),
    timer_index(0),
    draw(FALSE),
    mem_color_picker(NULL),
    swap_color_picker(NULL)
{
	LoadGraph * const g = this;

	// FIXME:
	// on configure, g->frames_per_unit = g->draw_width/(LoadGraph::NUM_POINTS);
	// knock FRAMES down to 5 until cairo gets faster
	g->frames_per_unit = 10;  // this will be changed but needs initialising
	g->fontsize = 8.0;
	g->rmargin = 3.5 * g->fontsize;
	g->indent = 24.0;

	g->type = type;
	g->n = 1;

	g->speed  = 1000;
	g->update_time = g->speed*1.0f / g->frames_per_unit;
	g->max_bitrate = 5;
	g->max_ujitter = 10;

	g->colors.resize(g->n);


	g->timer_index = 0;
	g->render_counter = (g->frames_per_unit - 1);
	g->draw = FALSE;

	g->main_widget = gtk_vbox_new (FALSE, FALSE);
	gtk_widget_set_size_request(g->main_widget, -1, LoadGraph::GRAPH_MIN_HEIGHT);
	gtk_widget_show (g->main_widget);

	g->disp = gtk_drawing_area_new ();
	gtk_widget_show (g->disp);
	g_signal_connect (G_OBJECT (g->disp), "expose_event",
			  G_CALLBACK (load_graph_expose), g);
	g_signal_connect (G_OBJECT(g->disp), "configure_event",
			  G_CALLBACK (load_graph_configure), g);
	g_signal_connect (G_OBJECT(g->disp), "destroy",
			  G_CALLBACK (load_graph_destroy), g);

	gtk_widget_set_events (g->disp, GDK_EXPOSURE_MASK);

	gtk_box_pack_start (GTK_BOX (g->main_widget), g->disp, TRUE, TRUE, 0);


	/* Allocate data in a contiguous block */
	g->data_block = std::vector<float>(g->n * LoadGraph::NUM_POINTS, -1.0f);
	for (guint i = 0; i < LoadGraph::NUM_POINTS; ++i)
	  g->data[i] = &g->data_block[0] + i * g->n;




	gtk_widget_show_all (g->main_widget);
}

void
load_graph_start (LoadGraph *g)
{


	if(!g->timer_index) {

		load_graph_update(g);

		g->timer_index = g_timeout_add (g->speed / g->frames_per_unit,
						load_graph_update,
						g);
	}

	g->draw = TRUE;
}

void
load_graph_stop (LoadGraph *g)
{
	/* don't draw anymore, but continue to poll */
	g->draw = FALSE;
}

void
load_graph_change_speed (LoadGraph *g,
			 guint new_speed)
{
	if (g->speed == new_speed)
		return;

	g->speed = new_speed;

	g_assert(g->timer_index);

	if(g->timer_index) {
		g_source_remove (g->timer_index);
		g->timer_index = g_timeout_add (g->speed / g->frames_per_unit,
						load_graph_update,
						g);
	}

	g->clear_background();
}


LoadGraphLabels*
load_graph_get_labels (LoadGraph *g)
{
	return &g->labels;
}

GtkWidget*
load_graph_get_widget (LoadGraph *g)
{
	return g->main_widget;
}

GtkWidget*
load_graph_get_mem_color_picker(LoadGraph *g)
{
	return g->mem_color_picker;
}

GtkWidget*
load_graph_get_swap_color_picker(LoadGraph *g)
{
	return g->swap_color_picker;
}
